package org.domeos.framework.engine.k8s.handler.impl;

import io.fabric8.kubernetes.api.model.NodeList;
import io.fabric8.kubernetes.api.model.PodSpec;
import io.fabric8.kubernetes.api.model.extensions.StatefulSet;
import io.fabric8.kubernetes.api.model.extensions.StatefulSetBuilder;
import io.fabric8.kubernetes.api.model.extensions.StatefulSetList;
import io.fabric8.kubernetes.api.model.extensions.StatefulSetSpec;
import io.fabric8.kubernetes.api.model.extensions.StatefulSetSpecBuilder;

import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.domeos.exception.K8sDriverException;
import org.domeos.framework.api.consolemodel.deployment.DeploymentDraft;
import org.domeos.framework.api.consolemodel.deployment.EnvDraft;
import org.domeos.framework.api.consolemodel.deployment.VersionString;
import org.domeos.framework.api.model.deployment.Deployment;
import org.domeos.framework.api.model.deployment.Policy;
import org.domeos.framework.api.model.deployment.Version;
import org.domeos.framework.api.model.deployment.related.DeploymentSnapshot;
import org.domeos.framework.api.model.deployment.related.LabelSelector;
import org.domeos.framework.api.model.deployment.related.VersionType;
import org.domeos.framework.api.model.loadBalancer.LoadBalancer;
import org.domeos.framework.engine.k8s.K8sPodSpecBuilder;
import org.domeos.framework.engine.k8s.handler.DeployResourceHandler;
import org.domeos.framework.engine.k8s.util.KubeUtils;
import org.domeos.framework.engine.k8s.util.ModelFormatUtils;
import org.domeos.framework.engine.model.CustomObjectMapper;
import org.domeos.framework.engine.model.CustomYamlObjectMapper;
import org.domeos.global.GlobalConstant;
import org.domeos.util.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

/**
 * 有状态容器添加
 * 
 * @author haqiaolong
 *
 */
public class StatefulSetDeployHandler implements
		DeployResourceHandler<StatefulSet> {

	private Deployment deployment;
	private KubeUtils kubeUtils;
	private String domeosServer;

	public StatefulSetDeployHandler(Deployment deployment, KubeUtils kubeUtils) {
		this.deployment = deployment;
		this.kubeUtils = kubeUtils;
	}

	public StatefulSetDeployHandler() {
	}

	public StatefulSetDeployHandler(Deployment deployment, KubeUtils kubeUtils,
			String domeosServer) {
		this.deployment = deployment;
		this.kubeUtils = kubeUtils;
		this.domeosServer = domeosServer;
	}

	@Override
	public StatefulSet build(Version version, List<LoadBalancer> loadBalancers,
			List<EnvDraft> extraEnvs) {
		if (deployment == null || version == null) {
			return null;
		}
		String dsName = null;
		Map<String, String> dsLabel = buildDSLabelWithSpecifyVersion(version);
		dsName = GlobalConstant.RC_NAME_PREFIX + deployment.getName() + "-v"
				+ version.getVersion();
		StatefulSet ss = new StatefulSetBuilder().withNewMetadata()
				.withName(dsName.toLowerCase()).withLabels(dsLabel)
				.withNamespace(deployment.getNamespace()).endMetadata().build();
		Map<String, String> annotations = new HashMap<>();
		annotations.put("deployName", deployment.getName());
		Map<String, String> podLabels = buildDSLabelWithSpecifyVersionAndLoadBalancer(
				version, loadBalancers);

		StatefulSetSpec dsSpec = new StatefulSetSpecBuilder().withNewTemplate()
				.withNewMetadata().withLabels(podLabels)
				.withAnnotations(annotations)
				.withDeletionGracePeriodSeconds(0L).endMetadata().endTemplate()
				.build();
		PodSpec podSpec = new K8sPodSpecBuilder(version, deployment, extraEnvs)
				.build();
		if (podSpec == null) {
			return null;
		}
		dsSpec.getTemplate().setSpec(podSpec);
		ss.setSpec(dsSpec);
		return ss;
	}

	@Override
	public StatefulSet create(Version version, List<EnvDraft> extraEnvs)
			throws K8sDriverException {
		StatefulSet statefulSet = build(version, null, extraEnvs);
		if (statefulSet == null || statefulSet.getSpec() == null) {
			String message = "build statefulSet set for deployment:"
					+ deployment.getName() + " failed";
			throw new K8sDriverException(message);
		}
		return kubeUtils.createStatefulSet(statefulSet);
	}

	@Override
	public void delete() throws K8sDriverException {
		StatefulSetList rsList = kubeUtils.listStatefulSet(buildDSLabel());
		if (rsList != null && rsList.getItems() != null) {
			for (StatefulSet ss : rsList.getItems()) {
				kubeUtils.deleteStatefulSet(ss.getMetadata().getName(), true);
			}
		}
	}

	@Override
	public StatefulSet scaleUp(Version version, int replicas)
			throws K8sDriverException {
		return scales(version, replicas);
	}

	@Override
	public StatefulSet scaleDown(Version version, int replicas)
			throws K8sDriverException {
		return scales(version, replicas);
	}

	private StatefulSet scales(Version version, int replicas)
			throws K8sDriverException {
		String deployName = GlobalConstant.RC_NAME_PREFIX
				+ deployment.getName() + "-v" + version.getVersion();
		Map<String, String> labels = new HashMap<>();
		for (LabelSelector labelSelector : version.getLabelSelectors()) {
			labels.put(labelSelector.getName(), labelSelector.getContent());
		}
		StatefulSet statefulSet = kubeUtils.statefulSetInfo(deployName);
		statefulSet.getSpec().getTemplate().getSpec().setNodeSelector(labels);
		return kubeUtils.patchStatefulSet(statefulSet.getMetadata().getName(),
				statefulSet);
	}

	@Override
	public StatefulSet update(Version version,
			List<LoadBalancer> loadBalancers, List<EnvDraft> extraEnvs,
			Policy policy, long eventId, int targetVersion)
			throws K8sDriverException {
		StatefulSet statefulSet = build(version, loadBalancers, extraEnvs);
		if (statefulSet != null) {
			return kubeUtils.patchStatefulSet(statefulSet.getMetadata()
					.getName(), statefulSet);
		} else {
			throw new K8sDriverException(
					"Error when build new statefulSet to update.");
		}
	}

	@Override
	public StatefulSet rollback(Version version,
			List<LoadBalancer> loadBalancers, List<EnvDraft> extraEnvs,
			Policy policy, long eventId, int targetVersion)
			throws K8sDriverException {
		StatefulSet statefulSet = build(version, loadBalancers, extraEnvs);
		if (statefulSet != null) {
			return kubeUtils.patchStatefulSet(statefulSet.getMetadata()
					.getName(), statefulSet);
		} else {
			throw new K8sDriverException(
					"Error when build new statefulSet to rollback.");
		}
	}

	@Override
	public Boolean abortUpdateOrRollBack() throws K8sDriverException {
		return null;
	}

	@Override
	public void abortScales() throws K8sDriverException {

	}

	@Override
	public StatefulSet abort() {
		return null;
	}

	@Override
	public void removeOtherDeploy(int versionId) throws K8sDriverException {
		Map<String, String> deploySelector = buildDSLabel();
		StatefulSetList statefulSetList = kubeUtils
				.listStatefulSet(deploySelector);
		if (statefulSetList == null || statefulSetList.getItems() == null
				|| statefulSetList.getItems().size() == 0
				|| statefulSetList.getItems().get(0) == null) {
			throw new K8sDriverException("no StatefulSet found");
		}
		for (StatefulSet ss : statefulSetList.getItems()) {
			String tmpVersion = ss.getMetadata().getLabels()
					.get(GlobalConstant.VERSION_STR);
			if (StringUtils.isBlank(tmpVersion)) {
				continue;
			}
			int tmpId = Integer.parseInt(tmpVersion);
			if (tmpId != versionId) {
				kubeUtils.deleteStatefulSet(ss.getMetadata().getName(), false);
			}
		}
	}

	@Override
	public VersionString getVersionString(DeploymentDraft deploymentDraft) {
		String dsName = GlobalConstant.RC_NAME_PREFIX + deployment.getName();
		Map<String, String> annotations = new HashMap<>();
		annotations.put("deployName", deploymentDraft.getDeployName());
		StatefulSet ss = new StatefulSetBuilder().withNewMetadata()
				.withName(dsName.toLowerCase())
				.withNamespace(deploymentDraft.getNamespace()).endMetadata()
				.withNewSpec().withNewTemplate().withNewMetadata()
				.withAnnotations(annotations)
				.withDeletionGracePeriodSeconds(0L).endMetadata().withNewSpec()
				.endSpec().endTemplate().endSpec().build();
		return getSSStr(ss, deploymentDraft.getVersionType());
	}

	@Override
	public VersionString getVersionString(Version version,
			List<LoadBalancer> loadBalancers, List<EnvDraft> extraEnvs) {
		StatefulSet ss = build(version, loadBalancers, extraEnvs);
		return getSSStr(ss, deployment.getVersionType());
	}

	@Override
	public List queryDesiredSnapshot() throws K8sDriverException {
		StatefulSetList ssList = kubeUtils.listStatefulSet(buildDSLabel());
        if (ssList == null || ssList.getItems() == null || ssList.getItems().size() == 0) {
            return null;
        }
        Map<Long, Long> snapshots = new HashMap<>();
        for (StatefulSet ss : ssList.getItems()) {
            if (ss == null || ss.getMetadata() == null || ss.getMetadata().getLabels() == null ||
                    !ss.getMetadata().getLabels().containsKey(GlobalConstant.VERSION_STR)) {
                continue;
            }
            Long version = Long.parseLong(ss.getMetadata().getLabels().get(GlobalConstant.VERSION_STR));
            NodeList nodeList = kubeUtils.listNode(ss.getSpec().getTemplate().getSpec().getNodeSelector());
            if (nodeList != null && !nodeList.getItems().isEmpty()) {
                int replicas = nodeList.getItems().size();
                if (snapshots.containsKey(version)) {
                    snapshots.put(version, snapshots.get(version) + replicas);
                } else {
                    snapshots.put(version, (long) replicas);
                }
            }

        }
        List<DeploymentSnapshot> snapshotList = new LinkedList<>();
        for (Map.Entry<Long, Long> entry : snapshots.entrySet()) {
            snapshotList.add(new DeploymentSnapshot(entry.getKey(), entry.getValue()));
        }
        return snapshotList;
	}

	private VersionString getSSStr(StatefulSet ss, VersionType versionType) {
		VersionString versionString = new VersionString();
		ModelFormatUtils.format(ss);
		try {
			if (versionType == VersionType.YAML) {
				ObjectMapper objectMapper = new CustomYamlObjectMapper();
				String deploymentStr = objectMapper.writeValueAsString(ss);
				versionString.setDeploymentStr(deploymentStr);
				ss.getSpec().getTemplate().setSpec(null);
				String deploymentStrHead = objectMapper.writeValueAsString(ss)
						+ "\n    spec:\n";
				versionString.setDeploymentStrHead(deploymentStrHead);
				versionString.setDeploymentStrTail("");
				versionString.setIndent(4);
				return versionString;
			} else if (versionType == VersionType.JSON) {
				ObjectMapper objectMapper = new CustomObjectMapper();
				objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
				String deploymentStr = objectMapper.writerFor(ss.getClass())
						.writeValueAsString(ss);
				versionString.setDeploymentStr(deploymentStr);
				ss.getSpec().getTemplate().setSpec(null);
				deploymentStr = objectMapper.writerFor(ss.getClass())
						.writeValueAsString(ss);
				String str[] = deploymentStr.split("\n");
				String headStr[] = new String[str.length - 3];
				String tailStr[] = new String[3];
				System.arraycopy(str, 0, headStr, 0, headStr.length);
				System.arraycopy(str, str.length - 3, tailStr, 0,
						tailStr.length);
				String deploymentStrHeader = StringUtils.join(headStr, "\n")
						+ "\n      \"spec\" : ";
				String deploymentStrtail = StringUtils.join(tailStr, "\n");
				versionString.setDeploymentStrHead(deploymentStrHeader);
				versionString.setDeploymentStrTail(deploymentStrtail);
				versionString.setIndent(6);
				return versionString;

			} else {
				return null;
			}
		} catch (IOException e) {
			return null;
		}
	}

	private Map<String, String> buildDSLabelWithSpecifyVersion(Version version) {
		Map<String, String> label = buildDSLabel();
		label.put(GlobalConstant.VERSION_STR,
				String.valueOf(version.getVersion()));
		return label;
	}

	private Map<String, String> buildDSLabel() {
		Map<String, String> label = new HashMap<>();
		label.put(GlobalConstant.DEPLOY_ID_STR,
				String.valueOf(deployment.getId()));
		return label;
	}

	private Map<String, String> buildDSLabelWithSpecifyVersionAndLoadBalancer(
			Version version, List<LoadBalancer> loadBalancers) {
		Map<String, String> label = buildDSLabel();
		label.put(GlobalConstant.VERSION_STR,
				String.valueOf(version.getVersion()));
		if (loadBalancers != null) {
			for (LoadBalancer loadBalancer : loadBalancers) {
				label.put(GlobalConstant.WITH_LB_PREFIX + loadBalancer.getId(),
						GlobalConstant.WITH_LB_VALUE);
			}
		}
		return label;
	}

}
